/**
 * Copyright(c) 2013 Jade Techonologies Co., Ltd.
 *
 * History:
 *   13-4-17 下午12:46 Created by sky
 */
package com.jade.bss.base.customer.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import com.jade.bss.base.customer.Customer;
import com.jade.bss.base.customer.CustomerDao;
import com.jade.bss.base.customer.CustomerException;
import com.jade.bss.base.customer.CustomerInfo;
import com.jade.framework.base.criterion.Criteria;
import com.jade.framework.base.criterion.Order;
import com.jade.framework.base.criterion.Restrictions;
import com.jade.framework.base.criterion.sql.SqlUtils;
import com.jade.framework.base.util.PagingArrayList;
import com.jade.framework.base.util.PagingList;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;

/**
 * 采用JPA方式实现的客户数据访问接口。
 *
 * @author sky
 * @version 1.0 13-4-17 下午12:46
 */
@Named ("bss_customerDao")
public class JpaCustomerDao
        implements CustomerDao
{
    static final String GSQL1 = "SELECT customer FROM Customer customer WHERE type=:type AND name=:name";
    static final String GSQL2 = "SELECT customer FROM Customer customer WHERE name=:name";
    @PersistenceContext (unitName = "default")
    protected EntityManager em;

    @Cacheable (value = "bss.customer.g", key = "#id")
    public Customer getCustomer(long id)
    {
        return em.find(Customer.class, id);
    }

    @SuppressWarnings ("unchecked")
    @Cacheable (value = "bss.customer.gt", key = "#type + '_' + #name")
    public Customer getCustomer(String type, String name)
    {
        Query query = em.createQuery(StringUtils.isNotBlank(type) ? GSQL1 : GSQL2);
        if (StringUtils.isNotBlank(type)) {
            query.setParameter("type", type);
        }
        query.setParameter("name", name);
        return SqlUtils.getSingleResult(query);
    }

    @Caching (evict = {@CacheEvict (value = "bss.customer.gc", key = "#customer.type"),
            @CacheEvict (value = "bss.customer.q", allEntries = true),
            @CacheEvict (value = "bss.customer.g", key = "#customer.id"),
            @CacheEvict (value = "bss.customer.gt", key = "#customer.type + '_' + #customer.name")})
    public long addCustomer(Customer customer)
            throws CustomerException
    {
        em.persist(customer);
        return customer.getId();
    }

    /**
     * agent、enterprise、domainId不能发生变更，否则会造成脏数据。
     *
     * @param customer 客户资料
     */
    @Caching (evict = {@CacheEvict (value = "bss.customer.gc", key = "#customer.type"),
            @CacheEvict (value = "bss.customer.q", allEntries = true),
            @CacheEvict (value = "bss.customer.g", key = "#customer.id"),
            @CacheEvict (value = "bss.customer.gt", key = "#customer.type + '_' + #customer.name")})
    public void updateCustomer(Customer customer)
            throws CustomerException
    {
        em.merge(customer);
    }

    @Cacheable (value = "bss.customer.q", key = "#criteria")
    public PagingList<Customer> queryCustomer(Criteria criteria)
    {
        String sql = "SELECT customer FROM Customer customer";
        return SqlUtils.sqlQuery(sql, em, criteria);
    }

    protected String[] getInfoCondition(Class... infos)
    {
        String[] condition = new String[]{"", ""};
        if (infos != null && infos.length > 0) {
            for (Class info : infos) {
                String className = info.getSimpleName();
                String name = className.substring(0, 1).toLowerCase() + className.substring(1);
                condition[0] += "," + className + " " + name;
                condition[1] += " AND customer.id=" + name + ".customerId";
            }
        }
        return condition;
    }

    protected PagingList<Customer> doQueryCustomer(Criteria criteria, Class... infos)
    {
        String sql = "SELECT customer FROM Customer customer";
        String[] array = getInfoCondition(infos);
        sql += array[0];
        if (StringUtils.isNotEmpty(array[1])) {
            sql += " WHERE " + array[1].substring(4);
        }
        return SqlUtils.sqlQuery(sql, em, criteria);
    }

    @Cacheable (value = "bss.customer.q", condition = "#root.args.length > 1", key = "#criteria + '_' + #infos")
    public PagingList<Customer> queryCustomer(Criteria criteria, Class... infos)
    {
        return doQueryCustomer(criteria, infos);
    }



    public PagingList<Customer> querySortDistance(double lat, double lng, double distance, Criteria criteria,
                                                  Class... infos)
    {
        String distanceSql = "6378.137 * 2 * ASIN(SQRT(POWER(SIN((" + lat + " - EnterpriseInfo.lat) * pi()/180/2), 2) + " +
                "COS(" + lat + "*pi()/180) * COS(EnterpriseInfo.lat*pi()/180) * POWER(SIN((" + lng + "-EnterpriseInfo.lng)*pi()/180/2), 2)))";
        String sql = "SELECT customer," + distanceSql + " as distance FROM Customer customer";
        String[] array = getInfoCondition(infos);
        sql += array[0];
        if (StringUtils.isNotEmpty(array[1])) {
            sql += " WHERE " + array[1].substring(4);
        }
        if (distance>0){
            criteria.add(Restrictions.lt(distanceSql, distance));
            criteria.addOrder(Order.asc("distance"));
        }
        PagingList<Object[]> list = SqlUtils.sqlQuery(sql, em, criteria);
        List<Customer> customers = new ArrayList<Customer>();
        for (Object[] objs : list) {
            Customer customer = (Customer) objs[0];
            customers.add(customer);
        }
        return new PagingArrayList<Customer>(list.getTotalCount(), customers);
    }

    @Cacheable (value = "bss.customer.gc", key = "#type")
    public long getCustomerCountByType(String type)
    {
        String sql = "SELECT count(*) FROM Customer WHERE type=:type AND status<>:status";
        Query query = em.createQuery(sql);
        query.setParameter("type", type);
        query.setParameter("status", Customer.STATUS_DELETED);
        return ((Number) query.getSingleResult()).longValue();
    }

    @Caching (evict = {@CacheEvict (value = "bss.customer.gc", key = "#customer.type"),
            @CacheEvict (value = "bss.customer.q", allEntries = true),
            @CacheEvict (value = "bss.customer.g", key = "#customer.id"),
            @CacheEvict (value = "bss.customer.gt", key = "#customer.type + '_' + #customer.name")})
    public void removeCustomer(Customer customer)
            throws CustomerException
    {
        Customer old = getCustomer(customer.getId());
        if (old != null)  {
            em.remove(old);
        }
    }

    @SuppressWarnings ("unchecked")
    @Cacheable (value = "bss.customer.info.g", key = "#customerId + '_' + #infoClass.name")
    public <Info extends CustomerInfo> Info getInfo(long customerId, Class infoClass)
    {
        return (Info) em.find(infoClass, customerId);
    }

    @Caching (
            evict = {@CacheEvict (value = "bss.customer.info.g",
                    key = "#info.customerId + '_' + #info.getClass().name")})
    public void addInfo(CustomerInfo info)
            throws CustomerException
    {
        em.persist(info);
    }

    @Caching (
            evict = {@CacheEvict (value = "bss.customer.info.g",
                    key = "#info.customerId + '_' + #info.getClass().name")})
    public void updateInfo(CustomerInfo info)
            throws CustomerException
    {
        em.merge(info);
    }

    @Caching (
            evict = {@CacheEvict (value = "bss.customer.info.g",
                    key = "#info.customerId + '_' + #info.getClass().name")})
    public void removeInfo(CustomerInfo info)
            throws CustomerException
    {
        CustomerInfo ci = em.find(info.getClass(), info.getCustomerId());
        if (ci != null) {
            em.remove(ci);
        }
    }

    public List<String> listTopTag(Criteria criteria)
    {
        String sql = "SELECT tag.name,count(tag.id) as c FROM Customer customer JOIN customer.tags tag";
        criteria.setGroup(Restrictions.group("tag", "name"));
        criteria.addOrder(Order.desc("c"));
        List<Object[]> objs = SqlUtils.sqlQueryWithoutCount(sql, em, criteria);
        List<String> tags = new ArrayList<String>(objs.size());
        for (Object[] array : objs) {
            tags.add((String) array[0]);
        }
        return tags;
    }

    public long getUserCount(Date beginDate, Date endDate, String type)
    {
        String sql = "SELECT COUNT(customer) FROM Customer customer WHERE 1=1 ";
        if(beginDate!=null)
            sql += " and creationDate>=:beginDate ";
        if(endDate!=null)
            sql += " and creationDate<=:endDate ";
        if(type!=null)
            sql += " and type=:type";
        Query query = em.createQuery(sql);
        if(beginDate!=null)
            query.setParameter("beginDate",beginDate);
        if(endDate!=null)
            query.setParameter("endDate",endDate);
        if(type!=null)
            query.setParameter("type",type);
        Object result = query.getSingleResult();
        if(result==null)
            return 0;
        else
            return (Long)result;
    }
}
